package com.jbwz.web.security.util;

import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RsaUtil {
    private static final String KEY_ALGORITHM = "RSA";
    //RSA本身的限制：最大加密明文大小 = 117
    private static final int MAX_ENCRYPT_BLOCK = 117;
    //RSA本身的限制：最大解密密文大小 = keySize / 8 = 128 或 256
    private static final int MAX_DECRYPT_BLOCK = 128;

    private static final String priKey = """
            MIICXQIBAAKBgQCyBXZ9b/Vd9Uwde1qACcY8L/8lUfIiCjBOU9caqhzji3bZEkkj
            ehTrO71VuQpGmOMQkpsEQ52n6aKG7W/2Yw+UQ5Yf5IlRiaYZkLSkp9iERgCkNfop
            6EW2KDJ28Muu4D+KRnyQrm7LbRZT5rXiNRKN5JwoKlfWzHzOcqz3GgR3awIDAQAB
            AoGBAICG2SlvL6OKek2+xXQzml/R91Amfb59xKqIdYDW2Myf4EqQnSVz+q5rDyNY
            2UZnE2jfr/PALY8RazonKvKkSpX4gKrCKMpYD5Xls4DvpNnQs+59PE/0Tt+0LlVP
            i6E15kte/Ynums/mns+CR7VPm7LcCSGPgg+llo4rlyuMd69BAkEA12ufVFFAwwZQ
            wf87g9XMLTTIH5uLl07sG39wk8doaa5kn3RI1t3sFBq0bnNyfjYuspVAtApNpjBH
            axjducOI0wJBANOOUV59Hx6m+wJhqiKovx88ccFYgk76tNL9z4NW0A2QYfuPXQHD
            ZG+8PpLnm7cznjIoKKpJ0WQ/TkByGsjfuAkCQBbArUFwm5B9uEXlOVoBWOFlc8Wy
            3hmLInD9Etzo8ORIoSQeWhsnsWmmMGyCERO3kVMXZJAfjngBHWn4ZGLcfjUCQEmD
            GalAxzCKi4x/TXRvuMHs6gZqcBVrrqvmgFY+M1uTQUj9vy7eq6bb/c5kZsqZSC5b
            oA3USsuP5+wi+kW036kCQQDUWXa7nunugd1WRDlMab/K5FA6HcQiY9HkPmSqS8H2
            F/R7BtzkztkWWCB16xiMrVRRKNBfFiEoZo3+H9Euzt2M
                                    """;
    private static final String pubKey = """
            MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyBXZ9b/Vd9Uwde1qACcY8L/8l
            UfIiCjBOU9caqhzji3bZEkkjehTrO71VuQpGmOMQkpsEQ52n6aKG7W/2Yw+UQ5Yf
            5IlRiaYZkLSkp9iERgCkNfop6EW2KDJ28Muu4D+KRnyQrm7LbRZT5rXiNRKN5Jwo
            KlfWzHzOcqz3GgR3awIDAQAB
                                    """;

    /**
     * PKCS1 privateKey
     *
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKeyPKCS1() throws Exception {
        byte[] keyBytes = Base64.decodeBase64(priKey);
        RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(keyBytes);
        RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent());
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(rsaPrivateKeySpec);
        return privateKey;
    }

    /**
     * PKCS1 privateKey
     *
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivateKeyPKCS8() throws Exception {
        byte[] keyBytes = Base64.decodeBase64(priKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        return privateK;
    }

    /**
     * 私钥加密
     *
     * @param datastr
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(String datastr) throws Exception {
        byte[] data = datastr.getBytes(StandardCharsets.UTF_8);
        PrivateKey privateK = getPrivateKeyPKCS1();
        Cipher cipher = Cipher.getInstance(privateK.getAlgorithm());  //获取算法
        cipher.init(Cipher.ENCRYPT_MODE, privateK);                     //设置加密模式，并指定私钥

        // 对数据分段加密
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buffer;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                buffer = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                buffer = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(buffer, 0, buffer.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 私钥解密
     */
    public static byte[] decryptByPrivateKey(String datastr) throws Exception {
        byte[] encryptedData = datastr.getBytes(StandardCharsets.UTF_8);
        byte[] keyBytes = Base64.decodeBase64(priKey);
        Key privateK = getPrivateKeyPKCS1();
        Cipher cipher = Cipher.getInstance(privateK.getAlgorithm());  //获取算法
        cipher.init(Cipher.DECRYPT_MODE, privateK);                     //设置解密模式，并指定私钥

        // 对数据分段解密
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buffer;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                buffer = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                buffer = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(buffer, 0, buffer.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥解密
     *
     * @param encryptedData 已加密数据
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(byte[] encryptedData) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(pubKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  //获取算法
        cipher.init(Cipher.DECRYPT_MODE, publicK);                      //设置解密模式，并指定公钥
        // 对数据分段解密
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buffer;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                buffer = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                buffer = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(buffer, 0, buffer.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

}
